﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Calculator
{
    /// <summary>
    /// The Calculator class holds all the logic for the calculator.
    /// 
    /// This calculator is a bit more simplistic than the one found in native
    /// windows. You must always click an operator and then equals. So the method
    /// for using the calculator would be number operator number equals and the result
    /// will be calculated. This is how the attached form should be set up as well.
    /// </summary>
    class Calculator
    {
        #region Display Event Stuff
        /// <summary>
        /// Delegate that is called to notify that the Display property of the
        /// class has been updated.
        /// </summary>
        /// <param name="sender">Object that is being updated</param>
        /// <param name="e">The event arguments are empty</param>
        public delegate void DisplayUpdatedHandler(object sender, EventArgs e);

        /// <summary>
        /// This simple event can be subscribed to in order to update a text box on a form or
        /// to do whatever else the application using it may need to do.
        /// </summary>
        public event DisplayUpdatedHandler DisplayUpdatedEvent = null;

        #endregion

        #region Properties

        /// <summary>
        /// The display property is updated with the calculator result string.
        /// </summary>
        private string display;
        public string Display
        {
            get { return display; }
        }

        /// <summary>
        /// The tape stores each formula line after each equals operation is performed
        /// </summary>
        private String[] tape;
        public String[] Tape
        {
            get { return tape; }
        }

        /// <summary>
        /// The TapePos property is used to track the position of the tape.
        /// 
        /// Useful if you are populating another array to store into a multiline textbox.
        /// </summary>
        private int tapePos;
        public int TapePos
        {
            get { return tapePos; }
        }

        #endregion

        //private float firstNumber;
        //private float secondNumber;
        private float result;
        private bool isFirst;
        private bool hasDecimal;
        private int tapeLength;
        private CalcOperationType currentType;
        private CalcOperationType previousType;

        public Calculator()
        {
            display = "0";
            tapeLength = 10;
            tape = new string[tapeLength];
            result = 0;
            tapePos = 0;
            isFirst = true;
            hasDecimal = false;
            currentType = CalcOperationType.NONE;
            previousType = CalcOperationType.NONE;
        }

        public void ReceiveNumber(int value)
        {
            if (isFirst)
            {
                display = value.ToString();
                isFirst = false;
            }
            else
            {
                display += value;
            }

            FireDisplayUpdatedEvent();
        }

        public void ProcessOperation(CalcOperationType type)
        {
            currentType = type;
            bool setDisplay = true;
            switch (currentType)
            {
                case CalcOperationType.ADD:
                    Add();
                    break;
                case CalcOperationType.SUBTRACT:
                    Subtract();
                    break;
                case CalcOperationType.MULTIPLY:
                    Multiply();
                    break;
                case CalcOperationType.DIVIDE:
                    Divide();
                    break;
                case CalcOperationType.SIGN:
                    DoSign();
                    setDisplay = false;
                    break;
                case CalcOperationType.DECIMAL:
                    DoDecimal();
                    setDisplay = false;
                    break;
                case CalcOperationType.EQUALS:
                    DoEquals();
                    break;
                case CalcOperationType.CLEAR:
                    DoClear();
                    break;
                case CalcOperationType.CLEARALL:
                    DoClearAll();
                    break;
            }
            if (setDisplay)
            {
                DoClear();
                display = result.ToString();
            }
            if (currentType == CalcOperationType.EQUALS)
                previousType = CalcOperationType.NONE;
            else
            {
                if (currentType == CalcOperationType.ADD || currentType == CalcOperationType.SUBTRACT
                    || currentType == CalcOperationType.MULTIPLY || currentType == CalcOperationType.DIVIDE)
                    previousType = currentType;
            }
            FireDisplayUpdatedEvent();
        }

        private void DoEqualsOperation(CalcOperationType type)
        {
            switch (type)
            {
                case CalcOperationType.ADD:
                    Add();
                    break;
                case CalcOperationType.SUBTRACT:
                    Subtract();
                    break;
                case CalcOperationType.MULTIPLY:
                    Multiply();
                    break;
                case CalcOperationType.DIVIDE:
                    Divide();
                    break;
            }
        }

        private void Add()
        {
            if (previousType == CalcOperationType.NONE)
            {
                result = float.Parse(display);
            }
            else
            {
                if (currentType == CalcOperationType.EQUALS)
                    result = result + float.Parse(display);
            }
        }

        private void Subtract()
        {
            if (previousType == CalcOperationType.NONE)
            {
                result = float.Parse(display);
            }
            else
            {
                if (currentType == CalcOperationType.EQUALS)
                    result = result - float.Parse(display);
            }
        }

        private void Multiply()
        {
            if (previousType == CalcOperationType.NONE)
            {
                result = float.Parse(display);
            }
            else
            {
                if (currentType == CalcOperationType.EQUALS)
                    result = result * float.Parse(display);
            }
        }

        private void Divide()
        {
            if (previousType == CalcOperationType.NONE)
            {
                result = float.Parse(display);
            }
            else
            {
                if (currentType == CalcOperationType.EQUALS)
                {
                    float tmp = float.Parse(display);
                    if (tmp != 0)
                        result = result / tmp;
                }
            }
        }

        private void DoDecimal()
        {
            if (!hasDecimal)
            {
                hasDecimal = true;
                isFirst = false;
                display += ".";
                currentType = CalcOperationType.NONE;
            }
        }

        private void DoEquals()
        {
            string tapeValue = result.ToString() + getOperator(previousType) + display + getOperator(CalcOperationType.EQUALS);
            DoEqualsOperation(previousType);
            tapeValue += result.ToString();
            if (tapePos == tape.Length)
                IncreaseTape();
            tape[tapePos] = tapeValue;
            tapePos++;
        }

        private void DoSign()
        {
            display = (float.Parse(display) * -1).ToString();
            isFirst = (float.Parse(display) == 0f);
        }

        private void IncreaseTape()
        {
            //increase the tape by 10% if we need more room for it
            int newTapeLength = (int)(tapeLength * 1.10);
            string[] tmp = new string[tapeLength];
            System.Array.Copy(tape, tmp, tapeLength);
            tape = new string[newTapeLength];
            System.Array.Copy(tmp, tape, tapeLength);
            tapeLength = newTapeLength;
        }

        private void FireDisplayUpdatedEvent()
        {
            if (DisplayUpdatedEvent != null)
            {
                DisplayUpdatedEvent(display, EventArgs.Empty);
            }
        }

        private void DoClear()
        {
            hasDecimal = false;
            isFirst = true;
            display = "0";
        }

        private void DoClearAll()
        {
            DoClear();
            currentType = CalcOperationType.NONE;
            result = 0;
        }

        private string getOperator(CalcOperationType type)
        {
            string op = string.Empty;
            switch (type)
            {
                case CalcOperationType.ADD:
                    op = " + ";
                    break;
                case CalcOperationType.SUBTRACT:
                    op = " - ";
                    break;
                case CalcOperationType.MULTIPLY:
                    op = " x ";
                    break;
                case CalcOperationType.DIVIDE:
                    op = " ÷ ";
                    break;
                case CalcOperationType.EQUALS:
                    op = " = ";
                    break;
            }
            return op;
        }
    }

    public enum CalcOperationType
    {
        ADD,
        SUBTRACT,
        MULTIPLY,
        DIVIDE,
        EQUALS,
        DECIMAL,
        SIGN,
        BKSPACE,
        CLEARALL,
        CLEAR,
        NONE
    }
}
